/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxScanner.h>

const static Sx::String DEFAULT_DELIMITER = " ";
const static char LF = '\n';
const static char CR = '\r';
const static char NULL_CHAR = '\0';
const static int INVALID_SCANNER_POS = -1;

Sx::Util::Scanner::Scanner() {
	this->data = String();
	this->delimiter = DEFAULT_DELIMITER;
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

Sx::Util::Scanner::Scanner(const Scanner& scanner) {
	this->data = scanner.data;
	this->delimiter = scanner.delimiter;
	this->substringLength = scanner.substringLength;
	this->undoBuffer = scanner.undoBuffer;
}

Sx::Util::Scanner::Scanner(String& string, char delimiter) {
	this->data = string;
	this->delimiter = String(delimiter);
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

Sx::Util::Scanner::Scanner(const String& string, char delimiter) {
	this->data = string;
	this->delimiter = String(delimiter);
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

Sx::Util::Scanner::Scanner(String& string, String delimiter) {
	this->data = string;
	this->delimiter = delimiter;
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

Sx::Util::Scanner::Scanner(const String& string, String delimiter) {
	this->data = string;
	this->delimiter = delimiter;
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

Sx::Util::Scanner::~Scanner() {}

bool Sx::Util::Scanner::hasNext() const {
	if ( this->data.length() == 0 || (static_cast<unsigned int>(this->substringLength) >= this->data.length() && this->substringLength != -1) )
		return false;
	else return true;
}

/*
 * This algorithm is extremely slow. This could be changed at any time to
 * improve the performance. The only requirement is that this function returns
 * the next token based on the defined delimiter of this scanner.
 */
Sx::String Sx::Util::Scanner::next() {
	//--------------------------------------------------------------------------------
	// If the Scanner's data has not been set return an empty string.
	//--------------------------------------------------------------------------------
	if ( this->data.length() == 0 ) return String();

	unsigned int length = this->data.length();
	String result = String();
	char* string = 0;
	char* token = 0;

	//--------------------------------------------------------------------------
	// This is if the Scanner's data has been set and a new strtok call needs to 
	// be made on the new data to start making new tokens.
	//--------------------------------------------------------------------------
	if ( this->substringLength == INVALID_SCANNER_POS ) {
		string = new char[length + 1];
		strcpy(string, this->data.toAscii().constData());
		token = strtok(string, this->delimiter.toAscii().constData());
		this->substringLength = strlen(token) + 1;
		this->undoBuffer.push_back(strlen(token) + 1);
		this->skipDelimiters();
	}
	else {
		//----------------------------------------------------------------------
		// If the next substring length to get the next token exceeds the length
		// of the data return an empty string and reset the Scanner.
		//----------------------------------------------------------------------
		if ( static_cast<unsigned int>(substringLength) > this->data.length() - 1 ) return String();

		//----------------------------------------------------------------------
		// Continue the tokenizing process
		//----------------------------------------------------------------------
		String substring = this->data.substring(this->substringLength, length);
		string = new char[length - this->substringLength + 1];
		strcpy(string, substring.toAscii().constData());
		token = strtok(string, this->delimiter.toAscii().constData());
		this->substringLength += strlen(string) + 1;
		this->undoBuffer.push_back(strlen(string) + 1);
		this->skipDelimiters();
	}

	result = String(token);
	delete [] string;
	return result;
}

Sx::String Sx::Util::Scanner::nextLine() {
	if ( this->substringLength == -1 ) this->substringLength = 0;

	String substr = this->data.substring(this->substringLength, this->data.length());
	unsigned int newSubLength = this->getLinePos(substr);
	String line = substr.substring(0, newSubLength);
	this->substringLength += newSubLength;
	this->undoBuffer.push_back(newSubLength);
	return line.trimmed();
}

Sx::String Sx::Util::Scanner::peek() {
	String token = this->next();
	this->undo(1);
	return token;
}

Sx::String Sx::Util::Scanner::pop() {
	return this->next();
}

char Sx::Util::Scanner::peekChar() {
	String peek = this->peek();
	if ( peek.length() != 0 ) return peek[0];
	else return NULL_CHAR;
}

char Sx::Util::Scanner::nextChar() {
	String token = this->peek();
	if ( token.length() == 0 ) return 0;
	else return token[0];
}

long Sx::Util::Scanner::nextLong() {
	return std::atol(this->next().toAscii().constData());
}

int Sx::Util::Scanner::nextInt() {
	return std::atoi(this->next().toAscii().constData());
}

double Sx::Util::Scanner::nextDouble() {
	return std::atof(this->next().toAscii().constData());
}

float Sx::Util::Scanner::nextFloat() {
	return static_cast<float>(this->nextDouble());
}

unsigned int Sx::Util::Scanner::getLength() const {
	if ( this->substringLength == INVALID_SCANNER_POS )
		return this->data.length();
	if ( this->substringLength != -1 && static_cast<unsigned int>(this->substringLength) >= this->data.length() )
		return 0;
	return this->data.length() - this->substringLength;
}

Sx::String Sx::Util::Scanner::getString() const {
	if ( this->substringLength != INVALID_SCANNER_POS && this->data.length() > 0 && static_cast<unsigned int>(this->substringLength) < this->data.length() )
		return this->data.substring(this->substringLength, this->data.length());
	else if ( this->substringLength == INVALID_SCANNER_POS && this->data.length() > 0  && static_cast<unsigned int>(this->substringLength) < this->data.length() )
		return this->data;
	else return std::string();
}

Sx::String Sx::Util::Scanner::getDelimiter() const {
	return this->delimiter;
}

void Sx::Util::Scanner::push(unsigned int steps) {
	this->undo(steps);
}

void Sx::Util::Scanner::increment(unsigned int steps) {
	for ( unsigned int i = 0; i < steps; i++ ) this->next();
}

void Sx::Util::Scanner::decrement(unsigned int steps) {
	this->undo(steps);
}

void Sx::Util::Scanner::scan(String data, String delimiter) {
	this->substringLength = INVALID_SCANNER_POS;
	this->delimiter = delimiter;
	this->data = data;
}

void Sx::Util::Scanner::reset() {
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

void Sx::Util::Scanner::setString(String string) {
	this->data = string;
	this->substringLength = INVALID_SCANNER_POS;
	this->undoBuffer = ArrayList<unsigned int>();
}

void Sx::Util::Scanner::setDelimiter(String delimiter) {
	this->delimiter = delimiter;
}

void Sx::Util::Scanner::operator ++ () {
	this->increment(1);
}

void Sx::Util::Scanner::operator -- () {
	this->decrement(1);
}

void Sx::Util::Scanner::undo(unsigned int steps) {
	if ( steps == 0 ) return;

	//--------------------------------------------------------------------------------
	// If the step undo size is larger than the number of elements in the undo
	// buffer then just reset the scanner.
	//--------------------------------------------------------------------------------
	if ( steps > this->undoBuffer.size() ) {
		this->substringLength = INVALID_SCANNER_POS;
		return;
	}

	//--------------------------------------------------------------------------------
	// Add up the total number characters to undo.
	//--------------------------------------------------------------------------------
	unsigned int totalUndoCount = 0;
	unsigned int erasedSum = 0;
	for ( unsigned int i = 0; i < steps; i++ ) {
		totalUndoCount += this->undoBuffer[this->undoBuffer.size() - 1];
		erasedSum += this->undoBuffer[this->undoBuffer.size() - 1];
		this->undoBuffer.removeAt(this->undoBuffer.size() - 1);
	}

	//--------------------------------------------------------------------------------
	// Subtract the total number of characters to undo from the substringLength.
	//--------------------------------------------------------------------------------
	if ( this->substringLength == INVALID_SCANNER_POS ) {
		unsigned int totalLength = 0;
		for ( unsigned int i = 0; i < this->undoBuffer.size(); i++ )
			totalLength += this->undoBuffer[i];
		this->substringLength = (totalLength + erasedSum) - totalUndoCount;
	}
	else
		this->substringLength -= totalUndoCount;
}

void Sx::Util::Scanner::skipDelimiters() {
	unsigned int totalDelimiters = 0;
	bool done = false;

	while ( done == false ) {
		if ( static_cast<unsigned int>(this->substringLength) >= this->data.length() ) break;
		char testChar = this->data[this->substringLength];
		String substring = this->data.substring(this->substringLength, this->data.length());
		for ( unsigned int i = 0; i < this->delimiter.length(); i++ ) {
			if ( testChar == this->delimiter[i] ) {
				totalDelimiters++;
				break;
			}

			if ( i == this->delimiter.length() - 1 ) {
				done = true;
				break;
			}
		}
		if ( done == false )
			this->substringLength++;
	}
	this->undoBuffer[this->undoBuffer.size() - 1] += totalDelimiters;
}

unsigned int Sx::Util::Scanner::getLinePos(String string) {
	String line;
	unsigned int length = string.length();
	unsigned int pos = 0;

	for ( unsigned int i = 0; i < length; i++ ) {
		if ( string[i] == LF ) {
			if ( i < length - 1 ) {
				if ( string[i+1] == CR ) pos++;
			}
			else if ( i == length - 1 ) return ++pos;
			return ++pos;
		}
		else if ( string[i] == CR ) {
			if ( i < length - 1) {
				if ( string[i+1] == LF ) pos++;
			}
			else if ( i == length - 1 ) return ++pos;
			return ++pos;
		}
		else {
			line += string[i];
			pos++;
		}
	}
	return pos;
}
